package com.bamboo.pistorage.provider.cos;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONObject;
import com.bamboo.core.enums.code.CodeCommonEnum;
import com.bamboo.core.exceptions.BambooException;
import com.bamboo.model.domain.StorageResultEntity;
import com.bamboo.model.enums.COSBucketEnum;
import com.bamboo.model.enums.CloudVendorEnum;
import com.bamboo.model.enums.FileTypeEnum;
import com.bamboo.pistorage.domain.DeleteResultEntity;
import com.bamboo.pistorage.domain.UploadParamEntity;
import com.bamboo.pistorage.domain.UploadResultEntity;
import com.bamboo.pistorage.factory.COSClientFactoryBuilder;
import com.bamboo.pistorage.factory.TencentCOSClientFactory;
import com.bamboo.pistorage.provider.ICosProvider;
import com.bamboo.pistorage.provider.IFileCheckerProvider;
import com.bamboo.pistorage.service.IStorageResultService;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.MultiObjectDeleteException;
import com.qcloud.cos.model.*;
import com.qcloud.cos.transfer.TransferManager;
import com.qcloud.cos.transfer.TransferManagerConfiguration;
import com.qcloud.cos.transfer.Upload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * @author chenzh
 * @description
 * @date 2025/3/26 16:18
 */
@Component
public class TencentCosProvider implements ICosProvider<String> {
    @Resource
    IStorageResultService storageResultService;
    @Resource
    IFileCheckerProvider fileCheckerProvider;

    final Logger logger = LoggerFactory.getLogger(TencentCosProvider.class);
    final TencentCOSClientFactory cosClientFactory = (TencentCOSClientFactory) COSClientFactoryBuilder.getCOSClientFactory(CloudVendorEnum.TENCENT);

    @Override
    public UploadResultEntity<String> upload(UploadParamEntity uploadParam) {
        // 检查文件是否满足上传条件
        fileCheckerProvider.check(uploadParam.getLocalFile());
        StorageResultEntity dbStorageResult = storageResultService.hasUploaded(CloudVendorEnum.TENCENT, uploadParam.getLocalFile());
        if (dbStorageResult != null) {
            UploadResultEntity<String> uploadResultEntity = new UploadResultEntity<>();
            uploadResultEntity.setLocalFile(uploadParam.getLocalFile());
            uploadResultEntity.setKey(dbStorageResult.getObjKey());
            uploadResultEntity.setBucket(uploadParam.getBucketEnum());
            uploadResultEntity.setExtra(dbStorageResult.getExtraInfo().toString());
            uploadResultEntity.setRequestId(dbStorageResult.getRequestId());
            return uploadResultEntity;
        }
        TransferManager transferManager = createTransferManager(cosClientFactory.getCOSClient(uploadParam.getBucketEnum()));
        try {
            this.configureTransferManager(transferManager);
            PutObjectRequest request = this.createPutRequest(uploadParam);
            UploadResult result = this.executeUpload(transferManager, request);
            UploadResultEntity<String> uploadResultEntity = this.buildResultEntity(uploadParam, result);
            Long rowId = this.saveStorage(uploadParam, uploadResultEntity);
            if (rowId > 0) {
                uploadResultEntity.setDbId(rowId);
                return uploadResultEntity;
            }
            // 从云存储删除数据
            DeleteResultEntity deleteResultEntity = this.deleteOne(uploadParam.getBucketEnum(), uploadParam.getKey());
            if (CollUtil.isEmpty(deleteResultEntity.getDeletedKeys())) {
                throw new BambooException(CodeCommonEnum.DATA_DELETE_FAILED);
            }
            throw new BambooException(CodeCommonEnum.SERVER_ERR);
        } catch (CosClientException | InterruptedException e) {
            logger.error("上传文件到腾讯云cos失败。", e);
            throw new BambooException(CodeCommonEnum.SERVER_ERR);
        } catch (Exception e) {
            // 非上传云存储的错误，说明上传成功了，此时执行删除操作
            // 从云存储删除数据
            DeleteResultEntity deleteResultEntity = this.deleteOne(uploadParam.getBucketEnum(), uploadParam.getKey());
            if (CollUtil.isEmpty(deleteResultEntity.getDeletedKeys())) {
                throw new BambooException(CodeCommonEnum.DATA_DELETE_FAILED);
            }
            throw new BambooException(e);
        }finally {
            // 指定参数为 true, 则同时会关闭 transferManager 内部的 COSClient 实例。
            // 指定参数为 false, 则不会关闭 transferManager 内部的 COSClient 实例。
            transferManager.shutdownNow(false);
        }
    }

    @Override
    public DeleteResultEntity deleteOne(COSBucketEnum bucketEnum, String key) {
        DeleteResultEntity deleteResultEntity = new DeleteResultEntity();
        try {
            cosClientFactory.getCOSClient(bucketEnum).deleteObject(bucketEnum.getBucketName(), key);
            deleteResultEntity.setDeletedKeys(Collections.singletonList(key));
            this.fetchDbRowAndDelete(key);
            return deleteResultEntity;
        } catch (CosClientException e) {
            logger.error("删除腾讯云文件失败。", e);
        }
        deleteResultEntity.setErrorKeys(Collections.singletonList(key));
        return deleteResultEntity;
    }

    @Override
    public DeleteResultEntity deleteAll(COSBucketEnum bucketEnum, List<String> keys) {
        DeleteResultEntity deleteResultEntity = new DeleteResultEntity();
        DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketEnum.getBucketName());
        // 设置要删除的key列表, 最多一次删除1000个
        // 传入要删除的文件名
        // 注意文件名不允许以正斜线/或者反斜线\开头，例如：
        // 存储桶目录下有a/b/c.txt文件，如果要删除，只能是 keyList.add(new KeyVersion("a/b/c.txt")),
        // 若使用 keyList.add(new KeyVersion("/a/b/c.txt"))会导致删除不成功
        List<DeleteObjectsRequest.KeyVersion> keyList = keys.stream()
                .map(DeleteObjectsRequest.KeyVersion::new)
                .collect(Collectors.toList());
        deleteObjectsRequest.setKeys(keyList);
        try {
            DeleteObjectsResult deleteObjectsResult = cosClientFactory.getCOSClient(bucketEnum)
                    .deleteObjects(deleteObjectsRequest);
            List<DeleteObjectsResult.DeletedObject> deletedObjects = deleteObjectsResult.getDeletedObjects();
            deletedObjects.forEach(dobj -> {
               deleteResultEntity.getDeletedKeys().add(dobj.getKey());
               // 查询数据库记录并删除
                try {
                    this.fetchDbRowAndDelete(dobj.getKey());
                } catch (Exception e) {
                    deleteResultEntity.getDbErrorKeys().add(e.getMessage());
                }
            });
            return deleteResultEntity;
        } catch (MultiObjectDeleteException mde) {
            // 如果部分删除成功部分失败, 返回 MultiObjectDeleteException
            List<DeleteObjectsResult.DeletedObject> deleteObjects = mde.getDeletedObjects();
            List<MultiObjectDeleteException.DeleteError> deleteErrors = mde.getErrors();
            deleteObjects.forEach(dobj -> {
                deleteResultEntity.getDeletedKeys().add(dobj.getKey());
            });
            deleteErrors.forEach(de -> {
                deleteResultEntity.getErrorKeys().add(de.getKey());
            });
        } catch (CosClientException e) {
            logger.error("删除腾讯云文件失败。", e);
        }
        return deleteResultEntity;
    }

    @Override
    public String getObjectUrl(COSBucketEnum bucketEnum, String objectKey) {
        URL objectUrl = cosClientFactory.getAnonymousCOSClient(bucketEnum)
                .getObjectUrl(bucketEnum.getBucketName(), objectKey);
        return objectUrl.toString();
    }

    private void fetchDbRowAndDelete(String key) {
        // 查询数据库是否存在
        StorageResultEntity storageResultEntity = storageResultService.fetchUploadResult(key, CloudVendorEnum.TENCENT);
        if (storageResultEntity != null) {
            if (!storageResultService.deleteUploadResult(storageResultEntity.getId())) {
                throw new BambooException(
                        CodeCommonEnum.DATA_DELETE_FAILED,
                        String.format("本地文件数据<id: %d>删除失败，请手动删除", storageResultEntity.getId())
                );
            }
        }
    }

    // 创建 TransferManager 实例，这个实例用来后续调用高级接口
    private TransferManager createTransferManager(COSClient cosClient) {
        // 自定义线程池大小，建议在客户端与 COS 网络充足（例如使用腾讯云的 CVM，同地域上传 COS）的情况下，设置成16或32即可，可较充分的利用网络资源
        // 对于使用公网传输且网络带宽质量不高的情况，建议减小该值，避免因网速过慢，造成请求超时。
        ExecutorService threadPool = Executors.newFixedThreadPool(32);

        // 传入一个 threadpool, 若不传入线程池，默认 TransferManager 中会生成一个单线程的线程池。
        return new TransferManager(cosClient, threadPool);
    }

    private void configureTransferManager(TransferManager transferManager) {
        TransferManagerConfiguration config = new TransferManagerConfiguration();
        config.setMultipartUploadThreshold(5 * 1024 * 1024);
        config.setMinimumUploadPartSize(1 * 1024 * 1024);
        transferManager.setConfiguration(config);
    }

    private PutObjectRequest createPutRequest(UploadParamEntity uploadParam) {
        PutObjectRequest request = new PutObjectRequest(
                uploadParam.getBucketEnum().getBucketName(),
                uploadParam.getKey(),
                uploadParam.getLocalFile()
        );
        request.setStorageClass(StorageClass.Standard_IA);
        return request;
    }

    private UploadResult executeUpload(TransferManager transferManager, PutObjectRequest request)
            throws CosClientException, InterruptedException {
        Upload upload = transferManager.upload(request);
        return upload.waitForUploadResult();
    }

    private UploadResultEntity<String> buildResultEntity(UploadParamEntity uploadParam, UploadResult result) {
        UploadResultEntity<String> entity = new UploadResultEntity<>();
        entity.setBucket(uploadParam.getBucketEnum());
        entity.setKey(uploadParam.getKey());
        entity.setLocalFile(uploadParam.getLocalFile());
        entity.setRequestId(result.getRequestId());
        entity.setExtra("");
        return entity;
    }

    private Long saveStorage(UploadParamEntity uploadParam, UploadResultEntity<String> uploadResultEntity) {
        // 保存到数据库，后续可考虑通过mq异步处理
        StorageResultEntity storageResultEntity = new StorageResultEntity();
        storageResultEntity.setRequestId(uploadResultEntity.getRequestId());
        storageResultEntity.setCloudVendorEnum(CloudVendorEnum.TENCENT);
        storageResultEntity.setObjKey(uploadResultEntity.getKey());
        storageResultEntity.setBucket(uploadParam.getBucketEnum());
        storageResultEntity.setExtraInfo(new JSONObject());
        storageResultEntity.setUrl(this.getObjectUrl(uploadParam.getBucketEnum(), uploadResultEntity.getKey()));
        String absolutePath = uploadParam.getLocalFile().getAbsolutePath();
        storageResultEntity.setFilename(FileUtil.getName(uploadParam.getLocalFile()));
        storageResultEntity.setFileMd5(SecureUtil.md5(uploadParam.getLocalFile()));
        storageResultEntity.setFileSize(uploadParam.getLocalFile().length());
        String suffix = FileUtil.getSuffix(uploadParam.getLocalFile());
        storageResultEntity.setFileType(FileTypeEnum.getEnumBySuffix(suffix));
        storageResultEntity.setSuffix(suffix);
        storageResultEntity.setLocalFilename(absolutePath);
        return storageResultService.saveUploadResult(storageResultEntity);
    }
}


